package com.sunday.gateway.security.authentication;

import com.sunday.authorization.security.tools.authorization.redis.operation.AuthorizationOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

/**
 * 自定义判定，加入redis支持，已进行SSO判定
 *
 * @author sunday
 * @see JwtReactiveAuthenticationManager
 * @since 2024/9/23
 */
@Slf4j
public class CustomJwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {

    private final ReactiveJwtDecoder jwtDecoder;

    private final AuthorizationOperation authorizationOperation;

    private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverterAdapter(
            new JwtAuthenticationConverter());

    public CustomJwtReactiveAuthenticationManager(ReactiveJwtDecoder jwtDecoder, AuthorizationOperation authorizationOperation) {
        Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");
        Assert.notNull(jwtDecoder, "stringRedisTemplate cannot be null");
        this.jwtDecoder = jwtDecoder;
        this.authorizationOperation = authorizationOperation;
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        // @formatter:off
        return Mono.justOrEmpty(authentication)
                .filter((a) -> a instanceof BearerTokenAuthenticationToken)
                .cast(BearerTokenAuthenticationToken.class)
                .map(BearerTokenAuthenticationToken::getToken)
                .flatMap(this.jwtDecoder::decode)
                /**
                 * 添加登录账号有效性得认证 SSO判定
                 * 可自行斟酌是否添加续期等动作
                 */
                .map(this::validateJwt)
                .flatMap(this.jwtAuthenticationConverter::convert)
                .cast(Authentication.class)
                .onErrorMap(JwtException.class, this::onError);
        // @formatter:on
    }

    /**
     * 进行token检测在redis中
     *
     * @param jwt
     * @return {@link Jwt}
     */
    private Jwt validateJwt(Jwt jwt) {
        if (!authorizationOperation.validateToken(jwt.getSubject(), jwt.getTokenValue())) {
//            OAuth2Error oAuth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN,
//                    String.format("Jwt invalid %s has been logged out, Invalid token %s", jwt.getSubject(), jwt.getTokenValue()),
//                    "https://tools.ietf.org/html/rfc6750#section-3.1");
//            throw new JwtValidationException(oAuth2Error.getDescription(), Arrays.asList(oAuth2Error));
            String message = String.format("Jwt invalid %s has been logged out, Invalid token %s", jwt.getSubject(), jwt.getTokenValue());
            log.info(message);
            throw new BadJwtException(message);
        }
        return jwt;
    }

    /**
     * Use the given {@link Converter} for converting a {@link Jwt} into an
     * {@link AbstractAuthenticationToken}.
     *
     * @param jwtAuthenticationConverter the {@link Converter} to use
     */
    public void setJwtAuthenticationConverter(
            Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter) {
        Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
        this.jwtAuthenticationConverter = jwtAuthenticationConverter;
    }

    private AuthenticationException onError(JwtException ex) {
        if (ex instanceof BadJwtException) {
            return new InvalidBearerTokenException(ex.getMessage(), ex);
        }
        return new AuthenticationServiceException(ex.getMessage(), ex);
    }

}
